/*
 * This file is Copyright 2002-2006 Loyola University Chicago,
 * Department of Computer Science and the Emerging Technologies Laboratory.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License via
 *    http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.citep.formats.ofx;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Stack;
import java.util.Hashtable;

public class SGMLParser extends ParserBase {
	@Override
	protected void unknownDecl(String data) {
		// TODO Auto-generated method stub
		
	}

	private static Pattern INTERESTING = Pattern.compile("[&<]");
	private static Pattern INCOMPLETE = Pattern.compile("&([a-zA-Z][a-zA-Z0-9]*|#[0-9]*)?|<([a-zA-Z][^<>]*|/([a-zA-Z][^<>]*)?|![^<>]*)?");
	private static Pattern ENTITYREF = Pattern.compile("&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]");
	private static Pattern CHARREF = Pattern.compile("&#([0-9]+)[^0-9]");
	private static Pattern STARTTAGOPEN = Pattern.compile("<[>a-zA-Z]");
	private static Pattern SHORTTAGOPEN = Pattern.compile("<[a-zA-Z][-.a-zA-Z0-9]*/");
	private static Pattern SHORTTAG = Pattern.compile("<([a-zA-Z][-.a-zA-Z0-9]*)/([^/]*)/");
	private static Pattern PICLOSE = Pattern.compile(">");
	private static Pattern ENDBRACKET = Pattern.compile("[<>]");
	private static Pattern TAGFIND = Pattern.compile("[a-zA-Z][-_.a-zA-Z0-9]*");
	private static Pattern ATTRFIND = Pattern.compile("\\s*([a-zA-Z_][-:.a-zA-Z_0-9]*)(\\s*=\\s*(\\'[^\\']*\\'|\"[^\"]*\"|[-a-zA-Z0-9./,:;+*%?!&$\\(\\)_#=~\\'\"@]*))?");

	private int verbose = 0;
	
	private String starttag_text = null;
	private Stack<String> stack = new Stack<String>();
	private String lasttag = "???";
	private boolean nomoretags = false;
	private boolean literal = false;
	private Hashtable<String,String> entityDefs = null;
	
	public SGMLParser(int verbose) {
		this.verbose = verbose;
	}

	public Hashtable<String,String> getEntityDefinitions() {
		if(entityDefs == null) {
			entityDefs = new Hashtable<String,String>();
			entityDefs.put("lt", "<");
			entityDefs.put("gt", ">");
			entityDefs.put("amp", "&");
			entityDefs.put("quot", "\"");
			entityDefs.put("apos", "'");
		}
		
		return entityDefs;
	}
	
	public void reset() {
		starttag_text = null;
		rawdata = "";
		stack = new Stack();
		lasttag = "???";
		nomoretags = false;
		literal = false;
		super.reset();
	}
	
	void setNoMoreTags() {
		nomoretags = literal = true;
	}
	
	void setLiteral() {
		literal = true;
	}
	
	public void feed(String data) throws Exception {
		rawdata = data;
		goAhead(false);
	}
	
	void close() throws Exception {
		goAhead(true);
	}
	
	void error(String msg) {
		//throw new SGMLParseException(msg);
	}
	
	void goAhead(boolean end) throws Exception {
		String buf = rawdata;
		Matcher matcher;
		int i = 0;
		int n = buf.length();
		
		while(i < n) {
			if(nomoretags) {
				//handleData(buf.substring(i,n));
				i = n;
				break;
			}
			
			int j;
			matcher = INTERESTING.matcher(buf);
			
			if(matcher.find(i)) {
				j = matcher.start();
			} else {
				j = n;
			}
			
			if(i < j)
				handleData(buf.substring(i,j));
			
			i = j;
			if(i == n)
				break;
			
			if(buf.charAt(i) == '<') {
								
				if(STARTTAGOPEN.matcher(buf.substring(i)).lookingAt()) {
					if(literal) {
						handleData(buf.substring(i,i+1));
						i++;
						continue;
					}
					int k = parseStartTag(i);
					
					if(k < 0)
						break;
					i = k;
					continue;
				}
				
				if(buf.startsWith("</", i)) {
					int k = parseEndTag(i);
					if(k < 0)
						break;
					i = k;
					literal = false;
					continue;
				}
				
				if(literal) {
					if(n > (i+1)) {
						handleData("<");
						i = i + 1;
					} else {
						//incomplete
						break;
					}
					continue;
				}
				
				if(buf.startsWith("<!--", i)) {

					int k = parseComment(i);
					if(k < 0)
						break;
					i = k;
					continue;
				}
				
				if(buf.startsWith("<?",i)) {
					int k = parsePi(i);
					if(k < 0)
						break;
					i += k;
					continue;
				}
				
				if(buf.startsWith("<!", i)) {
					int k = parseDeclaration(i);
					if(k < 0)
						break;
					i = k;
					continue;
				}
			} else if(buf.charAt(i) == '&') {
				if(literal) {
					handleData(buf.substring(i, i+1));
					i = i+1;
					continue;
				}
				
				matcher = CHARREF.matcher(buf);
				
				if(matcher.find(i)) {
					String name = matcher.group(1);
					
					handleCharRef(name);
					
					i = matcher.end(0);
				
					if(buf.charAt(i-1) != ';')
						i--;
					
					continue;
				}
				
				
				matcher = ENTITYREF.matcher(buf);
				
				if(matcher.find(i)) {
					String name = matcher.group(1);
					handleEntityRef(name);
					i = matcher.end(0);
					if(buf.charAt(i-1) != ';')
						i--;
					continue;
				}
			} else {
				error("neither < nor & ??");
			}
				
			matcher = INCOMPLETE.matcher(buf);
			if(!matcher.find(i)) {
				handleData(buf.substring(i,	i+1));
				i++;
				continue;
			}
				
			j = matcher.end(0);
				
			if(j == n)
				break;
			
			handleData(buf.substring(i, j));
			i = j;
		}
		
		//end while
		if(end && i < n) {
			handleData(buf.substring(i, n));
			i = n;
		}
		
		rawdata = buf.substring(i);
		// XXX if end: check for empty stack
		
		handleDocumentEnd();
	}
	
	public void handleDocumentEnd() {
		//override: when we are done parsing
	}
	
	int parsePi(int i) {
		String buf = rawdata;
		
		if(!rawdata.substring(i, i+2).equals("<?")) {
			error("Unexpected call to parsePi()");
		}
		
		Matcher matcher = PICLOSE.matcher(buf);
		if(!matcher.find(i+2))
			return -1;
		
		int j = matcher.start(0);
		handlePi(buf.substring(i+2, j));
		j = matcher.end(0);
		return j-i;
	}
	
	String getStarTagText() {
		return starttag_text;
	}
	
	int parseStartTag(int i) throws SGMLParserException {
		starttag_text = null;
		int start_pos = i;
		String buf = rawdata;
		
		Matcher matcher = SHORTTAGOPEN.matcher(buf);
		
		if(matcher.find(i)) {
			//SGML shorthand: <tag/data/ == <tag>data</tag>
			//XXX Can data contain &... (entity or char refs)?
			//XXX Can data contain < or > (tag characters)?
			//XXX Can there be whitespace before the first /?

			matcher = SHORTTAG.matcher(buf);
			if(!matcher.find(i))
				return -1;
			
			String tag = matcher.group(1);
			String data = matcher.group(2);
			
			starttag_text = "<" + tag + "/";
			tag = tag.toLowerCase();
			int k = matcher.end();
			
			finishShortTag(tag,data);
			starttag_text = buf.substring(start_pos, matcher.end(1) + 1);
			return k;
		}
		
		//XXX The following should skip matching quotes (' or ")
		matcher = ENDBRACKET.matcher(buf);
		
		if(!matcher.find(i+1))
			return -1;
		
		int j = matcher.start(0);
		
		// Now parse the data between i+1 and j into a tag and attrs
		Hashtable<String,String> attrs = new Hashtable<String,String>();

		int k;
		String tag;
		if(buf.substring(i, i+2) == "<>") {
			k = j;
			tag = lasttag;
		} else {
			matcher = TAGFIND.matcher(buf);
			if(!matcher.find(i+1))
				error("Unexpected call to parseStartTag()");
			k = matcher.end(0);
			tag = buf.substring(i+1,k).toLowerCase();
			lasttag = tag;
		}
		
		while(k < j) {
			matcher = ATTRFIND.matcher(buf);
			if(!matcher.find(k))
				break;
			
			String attrname = matcher.group(1);
			String rest = matcher.group(2);
			String attrvalue = matcher.group(3);
			
			int attrvalue_len = attrvalue.length();
			
			if(rest == null || rest.length() == 0) {
				attrvalue = attrname;
			} else if(attrvalue.length() > 1 &&
					attrvalue.charAt(0) == '\'' && '\'' == attrvalue.charAt(attrvalue_len) ||
					attrvalue.charAt(0) == '"' && attrvalue.charAt(attrvalue_len) =='"') {
				
				attrvalue = attrvalue.substring(1,attrvalue_len - 1);
			}
			
			attrs.put(attrname.toLowerCase(), attrvalue);
			k = matcher.end(0);
		}
		
		if(rawdata.charAt(j) == '>')
			j += 1;
		
		starttag_text = buf.substring(start_pos, j);
		finishStartTag(tag,attrs);
		return j;
	}
	
	int parseEndTag(int i) throws SGMLParserException {
		String buf = rawdata;
		Matcher matcher = ENDBRACKET.matcher(buf);
		
		if(!matcher.find(i+1))
			return -1;
		
		int j = matcher.start(0);
	
		if((i+2) > j)
			return -1;
		String tag = buf.substring(i+2, j).trim().toLowerCase();
				
		if(buf.charAt(j) == '>')
			j++;
		
		finishEndTag(tag);
		return j;
	}
	
	void finishShortTag(String tag, String data) throws SGMLParserException {
		finishStartTag(tag, new Hashtable<String,String>());
		handleData(data);
		finishEndTag(tag);
	}
	
	void finishStartTag(String tag, Hashtable<String,String> attrs) throws SGMLParserException {
		stack.push(tag);
		startElement(tag, attrs);
	}
	
	void startElement(String tag, Hashtable attributes) throws SGMLParserException {
		
	}
	
	void finishEndTag(String tag) throws SGMLParserException {
		if(tag == null || tag.length() == 0) {
			int found = stack.size() - 1;
			if(found < 0) {
				unknownEndTag(tag);
				return;
			}
		} else {
			if(!stack.contains(tag)) {
				reportUnbalanced(tag);
			}
			
			int found = stack.size();
			for(int i = 0; i < stack.size(); i++)
				if(stack.get(i).equals(tag))
					found = i;
		
			while(stack.size() > found) {
				tag = stack.pop();
				handleEndTag(tag);
			}
		}
	}
	
	void handleStartTag(String name, Hashtable<String,String>attributes) {
		
	}
	
	void handleEndTag(String name) throws SGMLParserException {
		
	}
	
	void reportUnbalanced(String tag) {
		System.out.println("Warning: UNBALANCED </" +tag + ">");
	}
	
	void handleCharRef(String name) {
		int n;
		try {
			n = Integer.parseInt(name);
		} catch(NumberFormatException e) {
			unknownCharRef(name);
			return;
		}
		
		if(!(0 <= n && n <= 255)) {
			unknownCharRef(name);
			return;
		}
		
		handleData("" + (char)n);
		
	}
	
	void handleEntityRef(String name) {
		if(getEntityDefinitions().contains(name)) {
			handleData(getEntityDefinitions().get(name));
		} else {
			unknownEntityRef(name);
		}
	}
	
	void handleData(String data) {
			
	}
	
	protected void handleComment(String comment) {
		
	}
	
	public void handleDecl(String comment) {
		
	}
	
	void handlePi(String data) {
		
	}
	
	void unknownStartTag(String tag, Hashtable attributes) {
		
	}
	
	void unknownEndTag(String tag) {
		
	}
	
	void unknownCharRef(String ref) {
		
	}
	
	void unknownEntityRef(String ref) {
		
	}
}
